#include "ib_udp_wrapper.h"
#include "basic_tool.h"
#include "config.h"
#include "mpz_tool.h"
#include "memreuse.h"
#include <math.h>


void * QueryThread (void * arg)
{
  Arg * obj = (Arg *)arg;
  if (obj == NULL)
	return NULL;

  obj->pIB->OnNewRequest ( obj->pRequest);
  return NULL;
}

void * ReceiveThread (void * arg)
{
  Arg * obj = (Arg *)arg;
  if (obj == NULL)
	return NULL;

  obj->pIB->ReceiveAck ();

  return NULL;
}

void * NewRequest (void * arg)
{
  pthread_t tThread1, tThread2;
  pthread_create(&tThread1, NULL, ::QueryThread, arg);
  pthread_create(&tThread2, NULL, ::ReceiveThread, arg);
  pthread_join(tThread1, NULL);
  pthread_join(tThread2, NULL);
  delete ((struct Arg *)arg)->pIB;
  delete (struct Arg *)arg;
  return NULL;
}

IBUdpWrapper::IBUdpWrapper()
{
  m_pUdpAgent = new UdpAgent;
  Setup();
//   pthread_mutex_init(&m_mutexSlidingWindow, NULL);
//   pthread_cond_init (&m_cvSlidingWindow, NULL);
}

IBUdpWrapper::~IBUdpWrapper()
{
  if (m_pUdpAgent != NULL)
	delete  m_pUdpAgent;
}

int IBUdpWrapper::Setup()
{
  
  string filename = Config::m_output_database_dir + Config::m_signature_protocol + "_ssn_counter_table.dat";
  ifstream infile(filename.c_str());
  cout<<"reading "<<filename<<endl;
  if (!infile)
	{
	  cerr<<"open "<<filename<<" failed"<<endl;
	}
  string strEncK_Ki;
  string strEncInd;
  int index;
  m_HashTable.Create(Config::m_HashTableSizeInBits);
  infile.exceptions (ifstream::eofbit | ifstream::failbit | ifstream::badbit );
  BasicTool::F_Time(BasicTool::START);
  mpz_t * mpzTag;
  char tmp[1024];
  size_t len;

  while(1)
	{
	  try
		{
		  infile.read((char *)&len, sizeof(size_t));
		  assert(len < 1024);
		  infile.read(tmp, len);
		  mpzTag = Memreuse::New();
		  MpzTool::Mpz_from_char(mpzTag, tmp, len);

		  infile.read((char *)&len, sizeof(size_t));
		  assert(len < 1024);
		  infile.read(tmp, len);
		  strEncK_Ki.assign(tmp, len);

// 		  infile.read((char *)&len, sizeof(size_t));
// 		  assert(len < 1024);
// 		  infile.read(tmp, len);
// 		  strEncInd.assign(tmp, len);

		  infile.read((char *)&index, sizeof(int));

		  //		  m_HashTable.Insert(mpzTag, strEncK_Ki, strEncInd);
		  m_HashTable.Insert(mpzTag, strEncK_Ki, index);
		}
	  catch (ifstream::failure e)
		{
		  infile.close();
		  cout<<"further reading failed "<<endl;
		  break;
		}
	}
  double tt = BasicTool::F_Time(BasicTool::STOP);
  cout<<tt/1000<<"s, "<<m_HashTable.TotalNum()<<" loaded with "<<m_HashTable.CollisionNum()<<" collisions"<<endl;
  return 0;
}

int IBUdpWrapper::Start()
{
  unsigned short portnum = Config::m_ib_port;
  cout<<"Start to listen on address "<<Config::m_ib_ip<<" port "<<portnum<<endl;
  if (m_pUdpAgent->Bind(Config::m_ib_ip, portnum) < 0)
	return -1;
  // m_pUdpAgent->SetNonBlocking();
  struct sockaddr_in sockaddClient;

  UdpRequest * pRequest = new UdpRequest; // multiple thread needs independent pRequest

  while(1)
	{
	  try
		{
		  int len = m_pUdpAgent->RecvFrom((char *)pRequest, sizeof(UdpRequest), sockaddClient);
		  //		  cout<<"Request from "<< inet_ntoa (sockaddClient.sin_addr)<<endl;
		  if (len < 0)
			throw 2;

		  char cType = pRequest->Type();
          if (cType != 1)
            {
              throw 1;
            }
          if (pRequest->SubRequestId() == 0 && pRequest->BlockId() == -1)
            {
              // start new thread
              cout<<"Request from "<< inet_ntoa (sockaddClient.sin_addr)<<" request id:"<<pRequest->RequestId()<<endl;
              pthread_t tThread;
              Arg * pArg = new Arg;
              IB * pIB = new IB;
              pIB->SetupUdp(portnum+2, sockaddClient);
              pIB->SetHashTable(&m_HashTable);
              pArg->pIB = pIB;
              pArg->pRequest = pRequest;
              pthread_create(&tThread, NULL, ::NewRequest, (void *)pArg);
            }
		}
	  catch(int e)
		{
		  cerr<<"error "<<endl;
		  if ( e == 3 )
			cerr<<"invalid request received"<<endl;
		  if ( e == 4 )
			cerr<<"non request received"<<endl;
		  if ( e == 1)
            cerr<<"different type of requests"<<endl;
		}
	}
  delete pRequest;
  return 0;
}
